/**
 * @author Simon Richardson - me@simonrichardson.info
 */
package org.osflash.signals;

import asunit.asserts.AssertEquals;
import asunit.asserts.AssertNotNull;
import asunit.asserts.AssertNull;
import asunit.asserts.AssertTrue;
import asunit.asserts.Fail;
import asunit.framework.IAsync;
import org.osflash.signals.events.GenericEvent;
import org.osflash.signals.events.IEvent;
import flash.display.Sprite;
import flash.errors.IllegalOperationError;

class MonoSignalTest {

	@:meta(Inject())
	public var async : IAsync;
	public var signal : MonoSignal;
	@:meta(Before())
	public function setUp() : Void {
		signal = new MonoSignal();
	}

	@:meta(After())
	public function tearDown() : Void {
		signal.removeAll();
		signal = null;
	}

	@:meta(Test())
	public function numListeners_is_0_after_creation() : Void {
		assertEquals(0, signal.numListeners);
	}

	@:meta(Test())
	public function dispatch_should_pass_event_to_listener_but_not_set_signal_or_target_properties() : Void {
		signal.add(async.add(checkGenericEvent, 10));
		signal.dispatch(new GenericEvent());
	}

	function checkGenericEvent(e : GenericEvent) : Void {
		assertNull("event.signal is not set by Signal", e.signal);
		assertNull("event.target is not set by Signal", e.target);
	}

	@:meta(Test(expects="flash.errors.IllegalOperationError"))
	public function add_two_listeners_should_throw_an_error() : Void {
		signal.add(checkGenericEvent);
		signal.add(checkGenericEvent);
	}

	@:meta(Test(expects="flash.errors.IllegalOperationError"))
	public function add_one_hundred_listeners_should_throw_an_error() : Void {
		var i : Int = 0;
		while(i < 100) {
			signal.add(checkGenericEvent);
			i++;
		}
	}

	@:meta(Test())
	public function add_one_listeners_then_remove_it_then_add_another_listener() : Void {
		signal.add(failIfCalled);
		signal.remove(failIfCalled);
		signal.add(checkGenericEvent);
		signal.dispatch(new GenericEvent());
	}

	@:meta(Test())
	public function addOnce_and_dispatch_should_remove_listener_automatically() : Void {
		signal.addOnce(newEmptyHandler());
		signal.dispatch(new GenericEvent());
		assertEquals("there should be no listeners", 0, signal.numListeners);
	}

	@:meta(Test())
	public function add_listener_then_remove_then_dispatch_should_not_call_listener() : Void {
		signal.add(failIfCalled);
		signal.remove(failIfCalled);
		signal.dispatch(new GenericEvent());
	}

	function failIfCalled(e : IEvent) : Void {
		fail("This event handler should not have been called.");
	}

	@:meta(Test())
	public function add_listener_then_remove_function_not_in_listeners_should_do_nothing() : Void {
		signal.add(newEmptyHandler());
		signal.remove(newEmptyHandler());
		assertEquals(1, signal.numListeners);
	}

	function newEmptyHandler() : Dynamic {
		return function(e : Dynamic) : Void {
		}
;
	}

	@:meta(Test(expects="flash.errors.IllegalOperationError"))
	public function addOnce_same_listener_twice_should_throw_error() : Void {
		var func : Dynamic = newEmptyHandler();
		signal.addOnce(func);
		signal.addOnce(func);
	}

	@:meta(Test())
	public function dispatch_non_IEvent_without_error() : Void {
		signal.addOnce(checkSprite);
		// Sprite doesn't have a target property,
		// so if the signal tried to set .target,
		// an error would be thrown and this test would fail.
		signal.dispatch(new Sprite());
	}

	function checkSprite(sprite : Sprite) : Void {
		assertTrue(Std.is(sprite, Sprite));
	}

	@:meta(Test())
	public function adding_a_listener_during_dispatch_should_not_call_it() : Void {
		signal.add(async.add(addListenerDuringDispatch, 10));
		signal.dispatch(new GenericEvent());
	}

	function addListenerDuringDispatch() : Void {
		try {
			signal.add(failIfCalled);
		}
		catch(error : IllegalOperationError) {
			assertTrue("there should be 1 listener", signal.numListeners == 1);
		}
		catch(error : Error) {
			fail("This error should not have been thrown.");
		}

	}

	@:meta(Test())
	public function removed_listener_should_return_slot() : Void {
		var listener : Dynamic = function() : Void {
		}
;
		var slot : ISlot = signal.add(listener);
		assertTrue("Slot is returned", slot == signal.remove(listener));
	}

	@:meta(Test())
	public function removed_listener_should_be_returned() : Void {
		var slot : ISlot = signal.add(function() : Void {
		}
);
		var listener : Dynamic = slot.listener;
		assertTrue("Slot is returned", slot == signal.remove(listener));
	}

	@:meta(Test())
	public function slot_params_are_null_when_created() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.add(listener);
		assertNull("params should be null", slot.params);
	}

	@:meta(Test())
	public function slot_params_should_not_be_null_after_adding_array() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.add(listener);
		slot.params = [];
		assertNotNull("params should not be null", slot.params);
	}

	@:meta(Test())
	public function slot_params_with_one_param_should_be_sent_through_to_listener() : Void {
		var listener : Dynamic = function() : Void {
			assertTrue(Std.is(args[0], Int));
			assertEquals(args[0], 1234);
		}
;
		var slot : ISlot = signal.add(listener);
		slot.params = [1234];
		signal.dispatch();
	}

	@:meta(Test())
	public function slot_params_with_multiple_params_should_be_sent_through_to_listener() : Void {
		var listener : Dynamic = function() : Void {
			assertTrue(Std.is(args[0], Int));
			assertEquals(args[0], 12345);
			assertTrue(Std.is(args[1], String));
			assertEquals(args[1], "text");
			assertTrue(Std.is(args[2], Sprite));
			assertEquals(args[2], slot.params[2]);
		}
;
		var slot : ISlot = signal.add(listener);
		slot.params = [12345, "text", new Sprite()];
		signal.dispatch();
	}

	@:meta(Test())
	public function verify_chaining_of_slot_params() : Void {
		var listener : Dynamic = function() : Void {
			assertEquals(args.length, 1);
			assertEquals(args[0], 1234567);
		}
;
		signal.add(listener).params = [1234567];
		signal.dispatch();
	}

	@:meta(Test())
	public function verify_chaining_and_concat_of_slot_params() : Void {
		var listener : Dynamic = function() : Void {
			assertEquals(args.length, 2);
			assertEquals(args[0], 12345678);
			assertEquals(args[1], "text");
		}
;
		signal.add(listener).params = [12345678].concat(["text"]);
		signal.dispatch();
	}

	@:meta(Test())
	public function verify_chaining_and_pushing_on_to_slot_params() : Void {
		var listener : Dynamic = function() : Void {
			assertEquals(args.length, 2);
			assertEquals(args[0], 123456789);
			assertEquals(args[1], "text");
		}
;
		// This is ugly, but I put money on somebody will attempt to do this!
		var slots : ISlot;
		(slots = signal.add(listener)).params = [123456789];
		slots.params.push("text");
		signal.dispatch();
	}

}

